#ifndef SERVER_USER_SERVER_USER_H
#define SERVER_USER_SERVER_USER_H

#include <brpc/server.h>
#include <butil/logging.h>

#include <memory>

#include "sms.h"
#include "etcd.h"
#include "channel.h"

#include "data_es.h"
#include "data_redis.h"
#include "mysql_user.h"

#include "user.pb.h"

namespace chat {
class UserServerImpl : public UserService {
   private:
    bool checkNickname(const std::string& nickname);
    bool checkPassword(const std::string& password);
    bool checkPhone(const std::string& phone);
    
    bool appendESData(const User user);

    /**
     * @brief 获取4位验证码
     */
    std::string getCode();

   public:
    UserServerImpl(const std::shared_ptr<elasticlient::Client>& es_client,
                   const std::shared_ptr<sw::redis::Redis>& redis_client,
                   const std::shared_ptr<odb::mysql::database>& odb_client,
                   const SpugSMSCodeSenderPtr sms_client,
                   const ServiceManagerPtr service_manager,
                   const std::string& file_service_name)
        : odb_user_(std::make_shared<UserTable>(odb_client)),
          redis_session_(std::make_shared<Session>(redis_client)),
          redis_status_(std::make_shared<Status>(redis_client)),
          redis_codes_(std::make_shared<Codes>(redis_client)),
          es_user_(std::make_shared<ESUser>(es_client)),
          sms_client_(sms_client),
          service_manager_(service_manager),
          file_service_name_(file_service_name) {}

    void UserRegister(::google::protobuf::RpcController* controller,
                      const ::chat::UserRegisterReq* request,
                      ::chat::UserRegisterRsp* response,
                      ::google::protobuf::Closure* done);
    void UserLogin(::google::protobuf::RpcController* controller,
                   const ::chat::UserLoginReq* request,
                   ::chat::UserLoginRsp* response,
                   ::google::protobuf::Closure* done);
    void GetPhoneVerifyCode(::google::protobuf::RpcController* controller,
                            const ::chat::PhoneVerifyCodeReq* request,
                            ::chat::PhoneVerifyCodeRsp* response,
                            ::google::protobuf::Closure* done);
    void PhoneRegister(::google::protobuf::RpcController* controller,
                       const ::chat::PhoneRegisterReq* request,
                       ::chat::PhoneRegisterRsp* response,
                       ::google::protobuf::Closure* done);
    void PhoneLogin(::google::protobuf::RpcController* controller,
                    const ::chat::PhoneLoginReq* request,
                    ::chat::PhoneLoginRsp* response,
                    ::google::protobuf::Closure* done);
    void GetUserInfo(::google::protobuf::RpcController* controller,
                     const ::chat::GetUserInfoReq* request,
                     ::chat::GetUserInfoRsp* response,
                     ::google::protobuf::Closure* done);
    void GetMultiUserInfo(::google::protobuf::RpcController* controller,
                          const ::chat::GetMultiUserInfoReq* request,
                          ::chat::GetMultiUserInfoRsp* response,
                          ::google::protobuf::Closure* done);
    void SetUserAvatar(::google::protobuf::RpcController* controller,
                       const ::chat::SetUserAvatarReq* request,
                       ::chat::SetUserAvatarRsp* response,
                       ::google::protobuf::Closure* done);
    void SetUserNickname(::google::protobuf::RpcController* controller,
                         const ::chat::SetUserNicknameReq* request,
                         ::chat::SetUserNicknameRsp* response,
                         ::google::protobuf::Closure* done);
    void SetUserDescription(::google::protobuf::RpcController* controller,
                            const ::chat::SetUserDescriptionReq* request,
                            ::chat::SetUserDescriptionRsp* response,
                            ::google::protobuf::Closure* done);
    void SetUserPhoneNumber(::google::protobuf::RpcController* controller,
                            const ::chat::SetUserPhoneNumberReq* request,
                            ::chat::SetUserPhoneNumberRsp* response,
                            ::google::protobuf::Closure* done);

   private:
    UserTablePtr odb_user_ = nullptr;

    SessionPtr redis_session_ = nullptr;
    StatusPtr redis_status_ = nullptr;
    CodesPtr redis_codes_ = nullptr;

    ESUserPtr es_user_ = nullptr;

    SpugSMSCodeSenderPtr sms_client_ = nullptr;

    ServiceManagerPtr service_manager_ = nullptr;
    std::string file_service_name_;
};

class UserServer {
   public:
    UserServer(const RegistryPtr& register_client,
               const DiscoveryPtr& discovery_client,
               const std::shared_ptr<brpc::Server> brpc_server,
               const std::shared_ptr<elasticlient::Client>& es_client,
               const std::shared_ptr<sw::redis::Redis> redis_client,
               const std::shared_ptr<odb::mysql::database> odb_client)
        : register_client_(register_client),
          discovery_client_(discovery_client),
          brpc_server_(brpc_server),
          es_client_(es_client),
          redis_client_(redis_client),
          odb_client_(odb_client) {}

    /**
     * @brief 启动rpc服务
     */
    void start() { brpc_server_->RunUntilAskedToQuit(); }

   private:
    RegistryPtr register_client_ = nullptr;
    DiscoveryPtr discovery_client_ = nullptr;

    std::shared_ptr<brpc::Server> brpc_server_ = nullptr;

    std::shared_ptr<elasticlient::Client> es_client_ = nullptr;
    std::shared_ptr<sw::redis::Redis> redis_client_ = nullptr;
    std::shared_ptr<odb::mysql::database> odb_client_ = nullptr;
};

using UserServerPtr = std::shared_ptr<UserServer>;

class UserServerBuilder {
   public:
    /**
     * @brief 创建服务注册客户端
     * @param register_name[in]: 服务名称
     * @param etcd_host[in]: etcd服务器 ip:port
     * @param access_host[in]: 外部访问 ip:port
     */
    void makeRegisterClient(const std::string& register_name,
                            const std::string& etcd_host,
                            const std::string& access_host);

    /**
     * @brief 创建rpc服务管理对象
     *
     * @param file_service_name[in]: 要关心的服务名称
     */
    void makeServiceManager(const std::string& file_service_name);

    /**
     * @brief 创建服务发现客户端
     * @param etcd_host[in]: etcd服务器 ip:port
     * @param base_service[in]: 服务发现时的根目录
     */
    void makeDiscoveryClient(const std::string& etcd_host,
                             const std::string& base_service);

    /**
     * @brief 创建 brpc 服务
     * @param port[in]: brpc 服务器端口
     * @param idle_timeout_sec[in]: 连接空闲超时时间，超时后连接关闭
     * @param num_threads[in]: 线程数量
     */
    void makeBrpcServer(const uint16_t port, const int idle_timeout_sec,
                        const int num_threads);

    /**
     * @brief 创建 ODB 客户端
     *
     * @param max_pool[in]: 最大连接池数量
     * @param user[in]: mysql用户名
     * @param password[in]: 对应用户名密码
     * @param db[in]: 要链接到的数据库名称
     * @param ip[in]: mysql服务器ip
     * @param port[in]: mysql服务器端口
     * @param charset[in]: 所采用的字符集
     */
    void makeOdbClient(const int max_pool, const std::string& user,
                       const std::string& password, const std::string& db,
                       const std::string& ip, const uint16_t port,
                       const std::string& charset);

    /**
     * @brief 创建redis客户端
     *
     * @param ip[in]: redis服务器ip
     * @param port[in]: redis服务器端口
     * @param db[in]: 库的编号，默认为0
     * @param keep_alive[in]: 是否设置长连接保活
     */
    void makeRedisClient(const std::string& ip, const uint16_t port,
                         const int db, const bool keep_alive);

    /**
     * @brief 创建ES客户端
     */
    void makeEsClient(const std::vector<std::string> host_list);

    void makeSMSClient(const std::string& api_key);

    /**
     * @brief 构造一个 UserServer对象 并返回
     */
    UserServerPtr build();

   private:
    RegistryPtr register_client_ = nullptr;

    ServiceManagerPtr service_manager_ = nullptr;
    DiscoveryPtr discovery_client_ = nullptr;
    std::string file_service_name_;

    std::shared_ptr<brpc::Server> brpc_server_ = nullptr;

    std::shared_ptr<odb::mysql::database> odb_client_ = nullptr;
    std::shared_ptr<sw::redis::Redis> redis_client_ = nullptr;
    std::shared_ptr<elasticlient::Client> es_client_ = nullptr;

    SpugSMSCodeSenderPtr sms_client_ = nullptr;
};
}  // namespace chat

#endif  // SERVER_USER_SERVER_USER_H